package com.wallet.my.service.impl;

import com.wallet.my.dao.mapper.AccountMapper;
import com.wallet.my.dao.mapper.TransRecordMapper;
import com.wallet.my.dao.pojo.AccountDO;
import com.wallet.my.dao.pojo.TransRecordDO;
import com.wallet.my.service.EthService;
import com.wallet.my.util.EthUtil;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.HDUtils;
import org.bitcoinj.wallet.DeterministicKeyChain;
import org.bitcoinj.wallet.DeterministicSeed;
import org.bitcoinj.wallet.UnreadableWalletException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.web3j.crypto.*;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.Request;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.EthGetBalance;
import org.web3j.protocol.core.methods.response.EthGetTransactionReceipt;
import org.web3j.protocol.core.methods.response.EthTransaction;
import org.web3j.protocol.core.methods.response.Web3ClientVersion;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;

import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.web3j.crypto.WalletUtils.generateWalletFile;

/**
 * 以太坊相关功能的接口实现类
 **/
@Service
public class EthServiceImpl implements EthService{

    @Autowired
    private Web3j web3j;                                               //加载初始化后的web3j对象，已经配置测试网kovan连接点
    @Autowired
    private EthUtil ethUtil;
    @Autowired
    private AccountDO accountDO;
    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private TransRecordMapper transRecordMapper;

    private final String path="D:\\360Downloads\\keystores\\";       //保存keystore文件的路径地址


    //获取连接点信息，测试是否连接成功
    @Override
    public String getClientVersion() {
        Web3ClientVersion web3ClientVersion = null;
        try {
            web3ClientVersion=web3j.web3ClientVersion().send();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return web3ClientVersion.getWeb3ClientVersion();
    }

//使用password参数，bip44协议创建钱包,    返回Bip39Wallet对象（属性：文件名walletFile+助记词mnemonic）
// 返回  json文件，也就是keystore。文件名：日期+address。   并保存在指定路径keystorePath下
    @Override
    public Map<String,Object> newWallet(String password, HttpSession session) {
        Bip39Wallet bip39Wallet=null;
        try {
            bip39Wallet = Bip44WalletUtils.generateBip44Wallet(password, new File(path));
        } catch (CipherException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        String keystorePath =path+bip39Wallet.getFilename();
        String mnemonic = bip39Wallet.getMnemonic();
        Credentials credentials = Bip44WalletUtils.loadBip44Credentials("", mnemonic);//这里的password要为空，否则生成seed不一样
        String privateKey = credentials.getEcKeyPair().getPrivateKey().toString(16);
        String publicKey = credentials.getEcKeyPair().getPublicKey().toString(16);
        String address = credentials.getAddress();
        HashMap<String, Object> map = new HashMap<>();
        map.put("keystorePath",keystorePath);
        map.put("mnemonic",mnemonic);
        map.put("privateKey",privateKey);
        map.put("publicKey",publicKey);
        map.put("address",address);
        map.put("accountPath","0");
        if (bip39Wallet == null){
            map.put("code", "1");
            map.put("message", "error");
        }else {
            map.put("code", "200");
            map.put("message","ok");
        }
        String uuid = ethUtil.getUUID();
        accountDO.setId(uuid);
        accountDO.setAddress(address);
        accountDO.setBalance("0");
        String username = session.getAttribute("username").toString();
        accountDO.setUsername(username);
        accountDO.setAccountPath("0");
        accountMapper.addAccount(accountDO);
        return map;
    }

//用主钥匙对生成子钥匙对，然后创建一个新的账户，使用accountPath来对子钥匙对进行标记
    @Override
    public Map<String, Object> newAccount(String mnemonic,HttpSession session) {
        String username = session.getAttribute("username").toString();
        int accountPath = accountMapper.getAccountNumByName(username)+1;
        int[] pathNumber = new int[]{accountPath};          //这里是要输入新建账号的序号。后面需要去数据库加重复判断，密码也需要加判断
        byte[] seed = MnemonicUtils.generateSeed(mnemonic, (String)null);         //助记词生成seed
        Bip32ECKeyPair masterKeypair = Bip32ECKeyPair.generateKeyPair(seed);      //seed生成主钥匙对
        Bip32ECKeyPair childECKeyPair = Bip32ECKeyPair.deriveKeyPair(masterKeypair, pathNumber);   //生成子钥匙对
        String privateKey = childECKeyPair.getPrivateKey().toString(16);
        String publicKey = childECKeyPair.getPublicKey().toString(16);
        String address = Numeric.prependHexPrefix(Keys.getAddress(childECKeyPair));
        HashMap<String, Object> map = new HashMap<>();
        map.put("mnemonic",mnemonic);
        map.put("privateKey",privateKey);
        map.put("publicKey",publicKey);
        map.put("address",address);
        map.put("accountPath",accountPath);
        if (childECKeyPair == null){               //这里还要加判断int【】已使用的判断记得
            map.put("code", "1");
            map.put("message", "error");
        }else {
            map.put("code", "200");
            map.put("message","ok");
        }
        String uuid = ethUtil.getUUID();
        accountDO.setId(uuid);
        accountDO.setAddress(address);
        accountDO.setBalance("0");
        accountDO.setUsername(username);
        accountDO.setAccountPath(String.valueOf(accountPath));
        accountMapper.addAccount(accountDO);
        return map;
    }

//    查询已添加的所有账号信息
    public List<AccountDO> getAllAccountByName(String username){

        List<AccountDO> allAccount = accountMapper.getAllAccountByName(username);
        return allAccount;
    }

//根据地址查询余额
    @Override
    public Map<String, Object> getBalance(String address) {
        EthGetBalance ethGetBalance = null;
        try {
            ethGetBalance = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send();
        } catch (IOException e) {
            e.printStackTrace();
        }
        BigDecimal bigDecimal = new BigDecimal(ethGetBalance.getBalance());
        BigDecimal balance = Convert.fromWei(bigDecimal, Convert.Unit.ETHER);
        HashMap<String, Object> map = new HashMap<>();
        map.put("balance", balance);
        if (balance == null){
            map.put("code", "1");
            map.put("message", "error");
        }else {
            map.put("code", "200");
            map.put("message","ok");
        }
        return map;
    }

//    转账

    @Override
    public Map<String, Object> transferETH(String fromAddr, String privateKey, String toAddr, BigDecimal money, BigInteger gasLimit, String data) {
        BigInteger nonce = ethUtil.getNonce(web3j,fromAddr);
        // 转账金额转换成wei
        BigInteger value = Convert.toWei(money, Convert.Unit.ETHER).toBigInteger();
        //获取全网平均gasprice
        BigInteger gasPrice = ethUtil.getMyGasPrice(web3j);
        // 构建交易
        Transaction transaction = Transaction.createEtherTransaction(fromAddr, nonce, gasPrice, gasLimit, toAddr, value);
        // 估算gasLimit
        BigInteger estimateGasLimit = ethUtil.getEstimateGasLimit(web3j,transaction);
        //查询余额
        BigDecimal myBalance = ethUtil.getMyBalance(web3j,fromAddr);
        // 判断余额是否充足
        boolean isEnough = ethUtil.balanceIsEnough(myBalance,value,estimateGasLimit,gasPrice);
        if (isEnough == false) {
            throw new RuntimeException("余额不足，请确认后再转账");
        } else {                     //调用签名方法，并发送交易
            String transHash = ethUtil.signAndSend(web3j,nonce, gasPrice, gasLimit, toAddr, value, data, privateKey);
            HashMap<String, Object> map = new HashMap<>();
            if (transHash == null) {
                map.put("code", "1");
                map.put("message", "error");
                map.put("error", transHash);
            }else {
                map.put("code", "200");
                map.put("message","ok");
                map.put("transHash",transHash);
            }
            return map;
            }
    }

//    根据hash查询交易信息,  主要是拿到交易时填的信息，没有 交易结果，实际gas消耗
    @Override
    public EthTransaction getTransInfoByHash(String transHash) {
        EthTransaction ethTransaction=null;
        try {
            ethTransaction = web3j.ethGetTransactionByHash(transHash).send();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ethTransaction;
    }

//    根据transHash查交易凭证   有交易结果，gas消耗，但是没有发起交易时填的 nonce，value，gasprice信息
    @Override
    public EthGetTransactionReceipt getTransReceipt(String transHash) {
        EthGetTransactionReceipt transReceipt=null;
        try {
            transReceipt = web3j.ethGetTransactionReceipt(transHash).send();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return transReceipt;
    }

    @Override
    //    获取当前库里所有地址
    public List<String> getAllAddress(){
        List<String> allAddress = accountMapper.getAllAddress();
        return allAddress;
    }

    //    获取当前用户的库里所有地址
    @Override
    public List<String> getAllAddressByName(String username) {
        List<String> allAddress = accountMapper.getAllAddressByName(username);
        return allAddress;
    }

    //    根据地址查询交易记录
    @Override
    public List<TransRecordDO> getTransRecordByAddress(String address) {
        List<TransRecordDO> transRecords = transRecordMapper.getTransRecordByAddress(address);
        return transRecords;
    }

//创建HD钱包，初始地址为 "m/44'/60'/0'/0/0"
    @Override
    public Map<String, Object> newHDWallet(HttpSession session){
        String accountPath= "M/44H/60H/0H/0/0";
        List<ChildNumber> childNumbers = HDUtils.parsePath(accountPath);
        byte[] initialEntropy = new byte[16];
        SecureRandom secureRandom = new SecureRandom();
        secureRandom.nextBytes(initialEntropy);
        String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy);

        ECKeyPair childECKeyPair = null;
        try {
            childECKeyPair = ethUtil.getECKeyPairByMnemonic(mnemonic, accountPath);
        } catch (UnreadableWalletException e) {
            e.printStackTrace();
        }
        String privateKey = childECKeyPair.getPrivateKey().toString(16);
        String publicKey = childECKeyPair.getPublicKey().toString(16);
        String address = Numeric.prependHexPrefix(Keys.getAddress(childECKeyPair));

        HashMap<String, Object> map = new HashMap<>();
        map.put("mnemonic",mnemonic);
        map.put("privateKey",privateKey);
        map.put("publicKey",publicKey);
        map.put("address",address);
        map.put("accountPath",accountPath);

        String username = session.getAttribute("username").toString();
        String uuid = ethUtil.getUUID();
        accountDO.setId(uuid);
        accountDO.setAddress(address);
        accountDO.setBalance("0");
        accountDO.setUsername(username);
        accountDO.setAccountPath(accountPath);
        accountMapper.addAccount(accountDO);
        return map;

    }

//    新建HD账户，去库里取名下多个地址了，自动累加生成账号地址
    @Override
    public Map<String, Object> newHDAccount(String mnemonic, HttpSession session) {
        String username = session.getAttribute("username").toString();
        int i=accountMapper.getAccountNumByName(username);
        String accountPath="M/44H/60H/0H/0/"+i;
        ECKeyPair childECKeyPair = null;
        try {
            childECKeyPair = ethUtil.getECKeyPairByMnemonic(mnemonic, accountPath);
        } catch (UnreadableWalletException e) {
            e.printStackTrace();
        }
        String privateKey = childECKeyPair.getPrivateKey().toString(16);
        String publicKey = childECKeyPair.getPublicKey().toString(16);
        String address = Numeric.prependHexPrefix(Keys.getAddress(childECKeyPair));

        HashMap<String, Object> map = new HashMap<>();
        map.put("mnemonic",mnemonic);
        map.put("privateKey",privateKey);
        map.put("publicKey",publicKey);
        map.put("address",address);
        map.put("accountPath",accountPath);

        String uuid = ethUtil.getUUID();
        accountDO.setId(uuid);
        accountDO.setAddress(address);
        accountDO.setBalance("0");
        accountDO.setUsername(username);
        accountDO.setAccountPath(accountPath);
        accountMapper.addAccount(accountDO);
        return map;
    }
}
